﻿/* ************************************************************
 * Namespace: JumpFleetGameLib.xCamera
 * Class:  Camera2D
 * Author:  Roy Penrod
 * 
 * Camera2D follows a moveable object around so the object
 * is always in the viewport.    
 * 
 * Attach a Camera2D object to the moveable object.
 * 
 * USING CAMERA2D:
 * 1) Create the moveable object you want to attach the
 *    2D camera to.  Make sure the object implements the 
 *    xCamera.IMoveableObject interface.
 *    
 * 2) Create the moveable map object you want to act as the
 *    map to move the 2D camera around.  Make sure the
 *    map ojbect implements the xCamera.IMoveableMap interface.
 *    
 * 3) Create a new Camera2D object.  In the constructor, pass
 *    the viewport width, viewport height, the moveable object 
 *    you want the camera attached to, and the moveable map
 *    object you want the camera to move around.
 *    
 *    Store the Camera2D object in a class member of the 
 *    moveable object.  For example:
 *    playerShip.Camera = new xCamera.Camera2D(...);
 *    
 * 4) During your moveable object's Update method, call
 *    the xCamera.Camera2D.MoveObject() method and pass
 *    it a Vector2 holding the movement distance and
 *    direction.  You also pass a flag to tell Camera2D
 *    whether the Xbox 360 Y axis gamepad data is inverted 
 *    or not.
 *    
 *    A positive X movement distance moves the movable
 *    object to the right.  A negative X movement distance
 *    moves the movable object to the left.
 *    
 *    With the invertAxisY flag set to false:
 *    A positive Y movement distance moves the movable 
 *    object up.  A negative movement distance moves the
 *    movable object down.
 *    
 *    With the invertAxisY flag set to true:
 *    A positive Y movement distance moves the moveable
 *    object down.  A negative movement distance moves the
 *    movable object up.
 *  
 * ************************************************************ */

using System;
using Microsoft.Xna.Framework;

namespace JumpFleetGameLib.xCamera
{
    public class Camera2D
    {
        #region MEMBERS

        // holds the moveable object
        private IMoveableObject _moveableObject;

        // holds the moveable map 
        private IMoveableMap _moveableMap;

        // viewport width
        private int _viewportWidth;

        // vieport height
        private int _viewportHeight;

        // viewport center point
        private Vector2 _viewportCenterPoint;

        #endregion


        #region PROPERTIES

        /// <summary>
        /// Returns the width of the viewport in pixels.  Type: int
        /// </summary>
        public int ViewportWidth
        {
            get { return _viewportWidth; }
        }

        /// <summary>
        /// Returns the height of the viewport in pixels.  Type: int
        /// </summary>
        public int ViewportHeight
        {
            get { return _viewportHeight; }
        }

        #endregion


        #region CONSTRUCTORS

        public Camera2D(int viewportWidth, int viewportHeight, IMoveableObject moveableObject, IMoveableMap moveableMap)
        {
            // assigns the parameters
            _viewportWidth = viewportWidth;
            _viewportHeight = viewportHeight;
            _moveableObject = moveableObject;
            _moveableMap = moveableMap;

            // sets the viewport center point
            _viewportCenterPoint = new Vector2(viewportWidth / 2, viewportHeight / 2);
        }

        #endregion


        #region METHODS

        /// <summary>
        /// Moves the moveable object and moveable map in relation to each other.
        /// </summary>
        /// <param name="movementDistance">The horizontal and vertical distance in pixels to move the moveable ojbect.  Type: float</param>
        public void MoveObject(Vector2 movementDistance, bool invertAxisY)
        {
            // gets the moveable object's new position
            Vector2 NewMoveableObjectPosition = new Vector2(_moveableObject.CameraAnchorPosition.X + movementDistance.X, _moveableObject.CameraAnchorPosition.Y - movementDistance.Y);
            Rectangle NewMoveableObjectRectangle = new Rectangle( (int)(_moveableObject.BoundingBox.X + movementDistance.X), (int)(_moveableObject.BoundingBox.Y - movementDistance.Y), _moveableObject.BoundingBox.Width, _moveableObject.BoundingBox.Height);

            // holds the distance to move each object
            float moveableObjectMovementDistanceX = 0;
            float moveableObjectMovementDistanceY = 0;
            float moveableMapMovementDistanceX = 0;
            float moveableMapMovementDistanceY = 0;            
            
            // checks to see if we're moving right
            if (movementDistance.X > 0)
            {
                // check the new moveable object X position
                // if it's less than or equal to the center of the viewport, move the moveable object the movementDistance.X
                if (NewMoveableObjectPosition.X <= _viewportCenterPoint.X)
                {
                    moveableObjectMovementDistanceX += movementDistance.X;
                }
                // if it's more than the center of the viewport, move the moveable object to the center point
                // and move the map the rest of the distance
                else if (NewMoveableObjectPosition.X > _viewportCenterPoint.X)
                {   
                    // if the viewport center point is greater than the moveable object camera anchor position
                    // move the moveable object to the center point
                    if (_viewportCenterPoint.X > _moveableObject.CameraAnchorPosition.X)
                        moveableObjectMovementDistanceX += _viewportCenterPoint.X - _moveableObject.CameraAnchorPosition.X;

                    // if the viewport center point is greater than the moveable object camera anchor position
                    // calculate the remaining distance after moving the moveable object to the center of the viewport
                    float remainingMovementDistanceX;
                    if (_viewportCenterPoint.X > _moveableObject.CameraAnchorPosition.X)
                        remainingMovementDistanceX = movementDistance.X - (_viewportCenterPoint.X - _moveableObject.CameraAnchorPosition.X);
                    else
                        remainingMovementDistanceX = movementDistance.X;
  
                    // if the right edge of the map is already at the edge of the viewport,
                    // move the moveable object toward the right edge of the viewport
                    if (_moveableMap.BoundingBox.Right == _viewportWidth)
                    {
                        
                        // check to see if right side of the moveable object is still within the viewport 
                        if (_moveableObject.BoundingBox.Right < _viewportWidth)
                            moveableObjectMovementDistanceX += remainingMovementDistanceX;                         
                    }
                    // if the right edge of the map is outside the edge of the viewport and the 
                    // difference between the right edge of the map and the right edge of the viewport
                    // is greater than or equal to the remainingMovementDistanceX, move the map to the left
                    else if ( (_moveableMap.BoundingBox.Right > _viewportWidth) && ( (_moveableMap.BoundingBox.Right - _viewportWidth) >= remainingMovementDistanceX ) )
                    {
                        moveableMapMovementDistanceX -= remainingMovementDistanceX;
                    }
                    // if the right edge of the map is outside the edge of the viewport and the
                    // difference between the right edge of the map and the right edge of the viewport is 
                    // less than the remainingMovementDistanceX, move the map to the left by the amount of the 
                    // difference, move the object to the right by the amount of remainingMovementDistanceX - difference
                    else if ( (_moveableMap.BoundingBox.Right > _viewportWidth) && ( (_moveableMap.BoundingBox.Right - _viewportWidth) < remainingMovementDistanceX ) )
                    {
                        moveableMapMovementDistanceX -= _moveableMap.BoundingBox.Right - _viewportWidth;
                        moveableObjectMovementDistanceX += remainingMovementDistanceX - (_moveableMap.BoundingBox.Right - _viewportWidth);
                    }                    
                }
            }

            // checks to see if we're moving left
            if (movementDistance.X < 0)
            {
                // check the new moveable object X position
                // if it's greater than or equal to the center of the viewport, move the moveable object the movementDistance.X
                if (NewMoveableObjectPosition.X >= _viewportCenterPoint.X)
                {
                    moveableObjectMovementDistanceX += movementDistance.X;
                }
                // if it's less than the center of the viewport, move the moveable object to the center point
                // and move the map the rest of the distance
                else if (NewMoveableObjectPosition.X < _viewportCenterPoint.X)
                {
                    // if the viewport center point is less than the moveable object camera anchor position
                    // move the moveable object to the center point
                    if (_viewportCenterPoint.X < _moveableObject.CameraAnchorPosition.X)
                        moveableObjectMovementDistanceX += _viewportCenterPoint.X - _moveableObject.CameraAnchorPosition.X;

                    // if the viewport center point is less than the moveable object camera anchor position
                    // calculate the remaining distance after moving the moveable object to the center of the viewport
                    float remainingMovementDistanceX;
                    if (_viewportCenterPoint.X < _moveableObject.CameraAnchorPosition.X)
                        remainingMovementDistanceX = movementDistance.X + (_moveableObject.CameraAnchorPosition.X - _viewportCenterPoint.X);
                    else
                        remainingMovementDistanceX = movementDistance.X;

                    // if the left edge of the map is already at the edge of the viewport,
                    // move the moveable object toward the left edge of the viewport
                    if (_moveableMap.BoundingBox.Left == 0)
                    {
                        // check to see if left side of the moveable object is still within the viewport 
                        if (_moveableObject.BoundingBox.Left > 0)
                            moveableObjectMovementDistanceX += remainingMovementDistanceX;
                    }
                    // if the left edge of the map is outside the edge of the viewport and the 
                    // difference between the left edge of the map and the left edge of the viewport
                    // is less than or equal to the remainingMovementDistanceX, move the map to the right
                    else if ((_moveableMap.BoundingBox.Left < 0) && (_moveableMap.BoundingBox.Left  <= remainingMovementDistanceX))
                    {
                        moveableMapMovementDistanceX -= remainingMovementDistanceX;
                    }
                        
                    // if the left edge of the map is outside the edge of the viewport and the
                    // difference between the left edge of the map and the left edge of the viewport is 
                    // less than the remainingMovementDistanceX, move the map to the right by the amount of the 
                    // difference, move the object to the left by the amount of remainingMovementDistanceX - difference
                    else if ((_moveableMap.BoundingBox.Left < 0) && (_moveableMap.BoundingBox.Left > remainingMovementDistanceX))
                    {
                        moveableMapMovementDistanceX -= _moveableMap.BoundingBox.Left;
                        moveableObjectMovementDistanceX += remainingMovementDistanceX - _moveableMap.BoundingBox.Left;
                    }                    
                }
            }

            // checks to see if it's receiving raw data from the Xbox 360 gamepad thumbstick
            if (invertAxisY == false)
            {
                // checks to see if we're moving down
                if (movementDistance.Y < 0)
                {
                    // check the new moveable object Y position
                    // if it's less than or equal to the center of the viewport, move the moveable object the movementDistance.Y
                    if (NewMoveableObjectPosition.Y <= _viewportCenterPoint.Y)
                    {
                        moveableObjectMovementDistanceY -= movementDistance.Y;
                    }
                    // if it's more than the center of the viewport, move the moveable object to the center point
                    // and move the map the rest of the distance
                    else if (NewMoveableObjectPosition.Y > _viewportCenterPoint.Y)
                    {
                        // if the viewport center point is greater than the moveable object camera anchor position
                        // move the moveable object to the center point
                        if (_viewportCenterPoint.Y > _moveableObject.CameraAnchorPosition.Y)
                            moveableObjectMovementDistanceY += _viewportCenterPoint.Y - _moveableObject.CameraAnchorPosition.Y;

                        // if the viewport center point is greater than the moveable object camera anchor position
                        // calculate the remaining distance after moving the moveable object to the center of the viewport
                        float remainingMovementDistanceY;
                        if (_viewportCenterPoint.Y > _moveableObject.CameraAnchorPosition.Y)
                            remainingMovementDistanceY = Math.Abs(movementDistance.Y + (_viewportCenterPoint.Y - _moveableObject.CameraAnchorPosition.Y));
                        else
                            remainingMovementDistanceY = Math.Abs(movementDistance.Y);
                        // if the bottom edge of the map is already at the edge of the viewport,
                        // move the moveable object toward the bottom edge of the viewport
                        if (_moveableMap.BoundingBox.Bottom == _viewportHeight)
                        {

                            // check to see if bottom side of the moveable object is still within the viewport 
                            if (_moveableObject.BoundingBox.Bottom < _viewportHeight)
                                moveableObjectMovementDistanceY += remainingMovementDistanceY;
                        }
                        // if the bottom edge of the map is outside the edge of the viewport and the 
                        // difference between the bottom edge of the map and the bottom edge of the viewport
                        // is greater than or equal to the remainingMovementDistanceY, move the map up
                        else if ((_moveableMap.BoundingBox.Bottom > _viewportHeight) && ((_moveableMap.BoundingBox.Bottom - _viewportHeight) >= remainingMovementDistanceY))
                        {
                            moveableMapMovementDistanceY -= remainingMovementDistanceY;
                        }
                        // if the bottom edge of the map is outside the edge of the viewport and the
                        // difference between the bottom edge of the map and the bottom edge of the viewport is 
                        // less than the remainingMovementDistanceY, move the map up by the amount of the 
                        // difference, move the object down by the amount of remainingMovementDistanceY - difference
                        else if ((_moveableMap.BoundingBox.Bottom > _viewportHeight) && ((_moveableMap.BoundingBox.Bottom - _viewportHeight) < remainingMovementDistanceY))
                        {
                            moveableMapMovementDistanceY -= _moveableMap.BoundingBox.Bottom - _viewportHeight;
                            moveableObjectMovementDistanceY += remainingMovementDistanceY - (_moveableMap.BoundingBox.Bottom - _viewportHeight);
                        }
                    }
                }


                // checks to see if we're moving up
                if (movementDistance.Y > 0)
                {
                    // check the new moveable object Y position
                    // if it's greater than or equal to the center of the viewport, move the moveable object the movementDistance.Y
                    if (NewMoveableObjectPosition.Y >= _viewportCenterPoint.Y)
                    {
                        moveableObjectMovementDistanceY -= movementDistance.Y;
                    }
                    // if it's less than the center of the viewport, move the moveable object to the center point
                    // and move the map the rest of the distance
                    else if (NewMoveableObjectPosition.Y < _viewportCenterPoint.Y)
                    {

                        // if the viewport center point is less than the moveable object camera anchor position
                        // move the moveable object to the center point
                        if (_viewportCenterPoint.Y < _moveableObject.CameraAnchorPosition.Y)
                            moveableObjectMovementDistanceY += _viewportCenterPoint.Y - _moveableObject.CameraAnchorPosition.Y;

                        // if the viewport center point is less than the moveable object camera anchor position
                        // calculate the remaining distance after moving the moveable object to the center of the viewport
                        float remainingMovementDistanceY;
                        if (_viewportCenterPoint.Y < _moveableObject.CameraAnchorPosition.Y)
                            remainingMovementDistanceY = -(movementDistance.Y + (_viewportCenterPoint.Y - _moveableObject.CameraAnchorPosition.Y));
                        else
                            remainingMovementDistanceY = -movementDistance.Y;

                        // if the top edge of the map is already at the edge of the viewport,
                        // move the moveable object toward the top edge of the viewport
                        if (_moveableMap.BoundingBox.Top == 0)
                        {

                            // check to see if top side of the moveable object is still within the viewport 
                            if (_moveableObject.BoundingBox.Top > 0)
                                moveableObjectMovementDistanceY += remainingMovementDistanceY;
                        }
                        // if the top edge of the map is outside the edge of the viewport and the 
                        // difference between the top edge of the map and the top edge of the viewport
                        // is less than or equal to the remainingMovementDistanceY, move the map down
                        else if ((_moveableMap.BoundingBox.Top < 0) && ((_moveableMap.BoundingBox.Top - 0) <= remainingMovementDistanceY))
                        {
                            moveableMapMovementDistanceY -= remainingMovementDistanceY;
                        }
                        // if the top edge of the map is outside the edge of the viewport and the
                        // difference between the top edge of the map and the top edge of the viewport is 
                        // greater than the remainingMovementDistanceY, move the map down by the amount of the 
                        // difference, move the object up by the amount of remainingMovementDistanceY - difference
                        else if ((_moveableMap.BoundingBox.Top < 0) && ((_moveableMap.BoundingBox.Top - 0) > remainingMovementDistanceY))
                        {
                            moveableMapMovementDistanceY -= _moveableMap.BoundingBox.Top - 0;
                            moveableObjectMovementDistanceY += remainingMovementDistanceY - (_moveableMap.BoundingBox.Top - 0);
                        }
                    }
                }
            }
            // checks to see if the Y axis from the Xbox 360 gamepad thumbstick is inverted to match screen coordinates
            else if (invertAxisY)
            {
                // checks to see if we're moving up
                if (movementDistance.Y < 0)
                {
                    // check the new moveable object Y position
                    // if it's greater than or equal to the center of the viewport, move the moveable object the movementDistance.Y
                    if (NewMoveableObjectPosition.Y >= _viewportCenterPoint.Y)
                    {
                        moveableObjectMovementDistanceY += movementDistance.Y;
                    }     
                    // if it's less than the center of the viewport, move the moveable object to the center point
                    // and move the map the rest of the distance
                    else if (NewMoveableObjectPosition.Y < _viewportCenterPoint.Y)
                    {                        
                        // if the viewport center point is less than the moveable object camera anchor position
                        // move the moveable object to the center point
                        if (_viewportCenterPoint.Y < _moveableObject.CameraAnchorPosition.Y)
                            moveableObjectMovementDistanceY -= _viewportCenterPoint.Y - _moveableObject.CameraAnchorPosition.Y;
                                                
                        // if the viewport center point is less than the moveable object camera anchor position
                        // calculate the remaining distance after moving the moveable object to the center of the viewport
                        float remainingMovementDistanceY;
                        if (_viewportCenterPoint.Y < _moveableObject.CameraAnchorPosition.Y)
                            remainingMovementDistanceY = movementDistance.Y + (_viewportCenterPoint.Y - _moveableObject.CameraAnchorPosition.Y);
                        else
                            remainingMovementDistanceY = movementDistance.Y;

                        // if the top edge of the map is already at the edge of the viewport,
                        // move the moveable object toward the top edge of the viewport
                        if (_moveableMap.BoundingBox.Top == 0)
                        {

                            // check to see if top side of the moveable object is still within the viewport 
                            if (_moveableObject.BoundingBox.Top > 0)
                                moveableObjectMovementDistanceY += remainingMovementDistanceY;
                        }                        
                        // if the top edge of the map is outside the edge of the viewport and the 
                        // difference between the top edge of the map and the top edge of the viewport
                        // is less than or equal to the remainingMovementDistanceY, move the map up
                        else if ((_moveableMap.BoundingBox.Top < 0) && ((_moveableMap.BoundingBox.Top - 0) <= remainingMovementDistanceY))
                        {
                            moveableMapMovementDistanceY -= remainingMovementDistanceY;
                        }                        
                        // if the top edge of the map is outside the edge of the viewport and the
                        // difference between the top edge of the map and the top edge of the viewport is 
                        // greater than the remainingMovementDistanceY, move the map down by the amount of the 
                        // difference, move the object up by the amount of remainingMovementDistanceY - difference
                        else if ((_moveableMap.BoundingBox.Top < 0) && ((_moveableMap.BoundingBox.Top - 0) > remainingMovementDistanceY))
                        {
                            moveableMapMovementDistanceY -= _moveableMap.BoundingBox.Top - 0;
                            moveableObjectMovementDistanceY += remainingMovementDistanceY - (_moveableMap.BoundingBox.Top - 0);
                        }                        
                    }                     
                }

                
                // checks to see if we're moving down
                if (movementDistance.Y > 0)
                {
                    // check the new moveable object Y position
                    // if it's less than or equal to the center of the viewport, move the moveable object the movementDistance.Y
                    if (NewMoveableObjectPosition.Y <= _viewportCenterPoint.Y)
                    {
                        moveableObjectMovementDistanceY += movementDistance.Y;
                    }
                    
                    // if it's greater than the center of the viewport, move the moveable object to the center point
                    // and move the map the rest of the distance
                    else if (NewMoveableObjectPosition.Y > _viewportCenterPoint.Y)
                    {                        
                        // if the viewport center point is greater than the moveable object camera anchor position
                        // move the moveable object to the center point
                        if (_viewportCenterPoint.Y > _moveableObject.CameraAnchorPosition.Y)
                            moveableObjectMovementDistanceY += _viewportCenterPoint.Y - _moveableObject.CameraAnchorPosition.Y;
                                                 
                        // if the viewport center point is greater than the moveable object camera anchor position
                        // calculate the remaining distance after moving the moveable object to the center of the viewport
                        float remainingMovementDistanceY;
                        if (_viewportCenterPoint.Y > _moveableObject.CameraAnchorPosition.Y)
                            remainingMovementDistanceY = movementDistance.Y + (_viewportCenterPoint.Y - _moveableObject.CameraAnchorPosition.Y);
                        else
                            remainingMovementDistanceY = movementDistance.Y;
                                                
                        // if the bottom edge of the map is already at the edge of the viewport,
                        // move the moveable object toward the bottom edge of the viewport
                        if (_moveableMap.BoundingBox.Bottom == _viewportHeight)
                        {

                            // check to see if bottom side of the moveable object is still within the viewport 
                            if (_moveableObject.BoundingBox.Bottom < _viewportHeight)
                                moveableObjectMovementDistanceY += remainingMovementDistanceY;
                        }                        
                        // if the bottom edge of the map is outside the edge of the viewport and the 
                        // difference between the bottom edge of the map and the bottom edge of the viewport
                        // is greater than or equal to the remainingMovementDistanceY, move the map up
                        else if ((_moveableMap.BoundingBox.Bottom > _viewportHeight) && ((_moveableMap.BoundingBox.Bottom - _viewportHeight) >= remainingMovementDistanceY))
                        {
                            moveableMapMovementDistanceY -= remainingMovementDistanceY;
                        }                        
                        // if the bottom edge of the map is outside the edge of the viewport and the
                        // difference between the bottom edge of the map and the bottom edge of the viewport is 
                        // less than the remainingMovementDistanceY, move the map up by the amount of the 
                        // difference, move the object down by the amount of remainingMovementDistanceY - difference
                        else if ((_moveableMap.BoundingBox.Bottom > _viewportHeight) && ((_moveableMap.BoundingBox.Bottom - _viewportHeight) < remainingMovementDistanceY))
                        {
                            moveableMapMovementDistanceY -= _moveableMap.BoundingBox.Bottom - _viewportHeight;
                            moveableObjectMovementDistanceY += remainingMovementDistanceY - (_moveableMap.BoundingBox.Bottom - _viewportHeight);
                        }                         
                    }                          
                }                
            }

            // moves the moveable object
            _moveableObject.Move( new Vector2(moveableObjectMovementDistanceX, moveableObjectMovementDistanceY) );

            // moves the movable map
            _moveableMap.Move( new Vector2(moveableMapMovementDistanceX, moveableMapMovementDistanceY) );
        }

        #endregion

    } // <-- end class
} // <-- end namespace
